package binance

import (
	"encoding/json"
	"fmt"
	"github.com/morgine/log"
	"github.com/shopspring/decimal"
	"io"
	"market/pkg/ws"
	"strconv"
	"strings"
)

const (
	DepthLevel5  Level = 5
	DepthLevel10 Level = 10
	DepthLevel20 Level = 20
)

type Level int

func SubscribeDepth(symbols []string, level Level, handle func(data *DepthData)) (cancel func() error) {
	ps := &params{
		Method: "SUBSCRIBE",
		Params: nil,
		ID:     100,
	}
	for _, symbol := range symbols {
		ps.Params = append(ps.Params, fmt.Sprintf("%s@depth%d@100ms", symbol, level))
	}
	sub, _ := json.Marshal(ps)
	ps.Method = "UNSUBSCRIBE"
	unsub, _ := json.Marshal(ps)
	return subscribeWs("wss://stream.binance.com:9443/stream", [][]byte{sub}, [][]byte{unsub}, func(msg *Message) {
		depth, err := msg.DepthData()
		if err != nil {
			log.Error.Println(err)
		} else {
			handle(depth)
		}
	})
}

type Message struct {
	Stream string          `json:"stream"`
	Data   json.RawMessage `json:"data"`
}

func (m *Message) DepthData() (data *DepthData, err error) {
	data = &DepthData{}
	err = json.Unmarshal(m.Data, data)
	if err != nil {
		return
	}
	// btcusdt@depth5@100ms.
	data.Symbol = m.Stream[:strings.Index(m.Stream, "@")]
	//data.Timestamp = m.Timestamp
	return
}

type DepthData struct {
	Symbol string        `json:"symbol"`
	Asks   []DepthOrders `json:"asks"`
	Bids   []DepthOrders `json:"bids"`
}

type DepthOrders []string

func (os DepthOrders) StringPrice() string {
	return os[0]
}

func (os DepthOrders) Price() decimal.Decimal {
	p, _ := decimal.NewFromString(os[0])
	return p
}

func (os DepthOrders) Size() decimal.Decimal {
	s, _ := decimal.NewFromString(os[1])
	return s
}

func (os DepthOrders) FloatSize() float64 {
	s, _ := strconv.ParseFloat(os[1], 64)
	return s
}

func subscribeWs(url string, connectMsgs, closeMsgs [][]byte, handle func(msg *Message)) (cancelFunc func() error) {
	var wsClient *ws.Client
	wsClient = ws.NewClient(url, func(w io.Writer) {
		for _, msg := range connectMsgs {
			_, err := w.Write(msg)
			if err != nil {
				log.Error.Println(err)
			}
		}
	}, func(message []byte) {
		msg := &Message{}
		err := json.Unmarshal(message, msg)
		if err != nil {
			log.Error.Println(err)
		} else {
			if len(msg.Data) == 0 {
				log.Info.Printf("binance: %s\n", message)
			} else {
				handle(msg)
			}
		}
	})
	wsClient.Run()
	return func() error {
		for _, msg := range closeMsgs {
			wsClient.Write(msg)
		}
		return wsClient.Close()
	}
}

type params struct {
	// {
	//"method": "SUBSCRIBE",
	//"params":
	//[
	//"btcusdt@aggTrade",
	//"btcusdt@depth"
	//],
	//"id": 1
	//}

	Method string   `json:"method"`
	Params []string `json:"params"`
	ID     int      `json:"id"`
}
